Skip to content

[OMEGA-490] Remove legacy antispam filter, fix dropped token limit notifications - #383

Open
paul-v-snet wants to merge 3 commits into
patch-to-v0.1.20from
omega-490-token-linit-notification-fix
Open

paul-v-snet wants to merge 3 commits into
patch-to-v0.1.20from
omega-490-token-linit-notification-fix

Conversation

@paul-v-snet

Copy link
Copy Markdown
Collaborator

Description

The issue was caused by a deprecated antispam mechanism that was removed from MeTTaClaw but, for some reason, still remained in Omega. After an internal discussion, we decided to remove it, which automatically fixes the issue.

How Has This Been Tested?

  • Ran live tests: all passed
  • Ran mock tests: all green

Checklist

  • PR contains autogenerated code
  • Self-review completed
  • Test scenarios above are passed with the version of the code from PR

@paul-v-snet

paul-v-snet commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Autotests results are here: https://github.com/singnet/Omega/actions/runs/37621008796

Comment thread src/channels.metta Outdated
(progn (change-state! &lastsend $msg)
(let $safemsg (string-replace $msg "\n" "\\n")
(let $temp (cut) (commChannelSend $safemsg)))) _))
(progn (change-state! &lastsend $msg)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need &lastsend here now that the deduplication check has been removed? It looks like the value is no longer read.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, now it's not used anywhere, but I just decided to keep it in case it will be useful in the future.
So we can keep it or remove it, that actually won't affect anything, or I can just add a comment with a clarification.
What do you prefer?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d prefer to remove it for now, since nothing reads it anymore.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed in 5910f6b

@TossSky

TossSky commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

@paul-v-snet, tested 5910f6b over singularitynet/omega:v0.1.20 and compared it with v0.1.20 as released. The model was GLM-5.2 on OpenRouter with maxOutputToken: 40 and reasoningMode: high, so reasoning used up the whole answer.

Notices per phase

before any message without input question 1 question 2 question 3
v0.1.20 2 0 1 0 0
this PR 4 26 19 15 15
this PR + patch below 0 0 1 1 1
  • A repeated notice is no longer dropped. On v0.1.20 questions 2 and 3 got none.
  • The loop also calls the model when there is no new message, and every empty answer becomes a send (lib_llm_ext.py:68). While the limit is hit, the user gets a notice every couple of seconds, whether they asked something or not.

Patch: one notice per user message

A new message (HUMAN-MSG: after the prompt delimiter, loop.metta:91) makes the notice pending, and a reply with send clears it. The first reply cut by the limit while the notice is pending becomes the notice. The change is in providers/, outside this diff, so I can't post it as a suggestion:

--- a/providers/lib_llm_ext.py
+++ b/providers/lib_llm_ext.py
@@ -4,3 +4,3 @@ from typing import Optional, Tuple, Dict, Any
 from config import config_get_by_key
-from src.helper import quote_arg
+from src.helper import quote_arg, split_command_blocks
 from src.logger import get_logger
@@ -69,2 +69,26 @@ def _llm_empty_response_command() -> str:
 
+_limit_notice_pending = False
+
+def _sends_to_user(raw: str) -> bool:
+    text = raw.replace("_quote_", '"').replace("_newline_", "\n")
+    for block in split_command_blocks(text):
+        command = block.lstrip().lstrip("(").split(maxsplit=1)
+        if command and command[0].rstrip(")") == "send":
+            return True
+    return False
+
+def _reply_or_limit_notice(content: str, raw: str, cut_by_limit: bool) -> str:
+    global _limit_notice_pending
+    _, usermsg = _split_system_user(content)
+    if "HUMAN-MSG:" in usermsg:
+        _limit_notice_pending = True
+    if raw:
+        if _sends_to_user(raw):
+            _limit_notice_pending = False
+        return raw
+    if cut_by_limit and _limit_notice_pending:
+        _limit_notice_pending = False
+        return _llm_empty_response_command()
+    return raw
+
 def _split_system_user(content: str) -> Tuple[str, str]:
@@ -190,4 +214,3 @@ class AIProvider(AbstractAIProvider):
                 logger.warning("LLM returned an empty response")
-                if finish_reason == "length":
-                    raw = _llm_empty_response_command()
+            raw = _reply_or_limit_notice(content, raw, finish_reason == "length")
             resp = self._clean_text(raw)
--- a/providers/asione.py
+++ b/providers/asione.py
@@ -77,4 +77,3 @@ class ASIOneProviderImpl(llm.AIProvider):
                 logger.warning("LLM returned an empty response")
-                if finish_reason == "length":
-                    raw = llm._llm_empty_response_command()
+            raw = llm._reply_or_limit_notice(content, raw, finish_reason == "length")
             resp = self._clean_text(raw)
--- a/providers/openai.py
+++ b/providers/openai.py
@@ -64,4 +64,3 @@ class OpenAIProviderImpl(llm.AIProvider):
                 logger.warning("LLM returned an empty response")
-                if incomplete_reason == "max_output_tokens":
-                    raw = llm._llm_empty_response_command()
+            raw = llm._reply_or_limit_notice(content, raw, incomplete_reason == "max_output_tokens")
             return self._clean_text(raw)
--- a/Autotests/unit/test_llm_budget.py
+++ b/Autotests/unit/test_llm_budget.py
@@ -57,3 +57,9 @@ asione = _MODULES["asione"]
 
-PROMPT = "You are an agent. :-:-:-: Write an empty line to /tmp/paths.txt"
+PROMPT = "You are an agent. :-:-:-: (HUMAN-MSG: Write an empty line to /tmp/paths.txt)"
+IDLE_PROMPT = "You are an agent. :-:-:-: "
+
+
+@pytest.fixture(autouse=True)
+def no_pending_limit_notice():
+    llm._limit_notice_pending = False
 
@@ -158,2 +164,41 @@ def test_asione_empty_reply_out_of_budget_is_explained():
 
+@pytest.mark.parametrize("make, response", [
+    (make_openrouter, lambda: chat_response("", "length")),
+    (make_asione, lambda: chat_response("", "length")),
+    (make_openai, lambda: responses_response("", "incomplete", "max_output_tokens")),
+])
+@pytest.mark.parametrize("prompt", [IDLE_PROMPT, "You are an agent. :-:-:-:  DO NOT RE-SEND OR SPAM!"])
+def test_empty_reply_out_of_budget_without_a_user_message_sends_nothing(make, response, prompt):
+    assert make(FakeCreate(response())).chat(prompt) == ""
+
+
+def test_limit_notice_comes_once_after_a_step_without_an_answer():
+    provider = make_openrouter(FakeCreate(
+        chat_response('(query "hash maps")', "length"),
+        chat_response("", "length"),
+        chat_response("", "length"),
+    ))
+    assert provider.chat(PROMPT) == '(query "hash maps")'
+    assert sent_text(provider.chat(IDLE_PROMPT)) == llm.LLM_EMPTY_RESPONSE_MESSAGE
+    assert provider.chat(IDLE_PROMPT) == ""
+
+
+@pytest.mark.parametrize("answer", ['(send "hi")', 'send "hi"', 'Sure.\nsend "hi"', '(query "x")\n(send "hi")'])
+def test_limit_notice_is_skipped_once_the_user_got_an_answer(answer):
+    provider = make_openrouter(FakeCreate(
+        chat_response(answer, "stop"),
+        chat_response("", "length"),
+    ))
+    assert provider.chat(PROMPT) == answer
+    assert provider.chat(IDLE_PROMPT) == ""
+
+
+def test_every_user_message_cut_by_the_limit_gets_its_own_notice():
+    provider = make_openrouter(FakeCreate(*(chat_response("", "length") for _ in range(4))))
+    assert sent_text(provider.chat(PROMPT)) == llm.LLM_EMPTY_RESPONSE_MESSAGE
+    assert provider.chat(IDLE_PROMPT) == ""
+    assert sent_text(provider.chat(PROMPT)) == llm.LLM_EMPTY_RESPONSE_MESSAGE
+    assert provider.chat(IDLE_PROMPT) == ""
+
+
 @pytest.mark.parametrize("effort, max_tokens, want_budget, want_enabled", [

With the patch:

  • Each question cut by the limit gets one notice, and nothing comes without input (last row of the table).
  • When the limit cuts only some steps, a notice never comes after an answer.
  • Answers with the default config don't change.
  • Autotests/unit passes 88 of 88. The 12 new cases fail without the patch.

Repeated replies now reach the user

With the default config, one answer was followed by "No new user input. Standing by." twice in a row, and one request got pong twice. v0.1.20 drops the second copy. This is the cost of removing the check, which was agreed, so I'm leaving it as a note.

A retry with the same text is ignored

loop.metta:85 treats a message equal to the previous one as no new input, so the same text sent twice gets no answer the second time. It happens on v0.1.20 too and is outside the diff. After a token limit notice a user will likely retry with the same text, so this may deserve its own ticket.

Leftover &lastsend mention

The docstring of test_ws_resume_dedup_ws_mock.py still describes the send-level &lastsend guard, which this PR removes.

Verdict: FAIL as is, PASS with the patch above.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants